﻿// Copyright 2004-2011 Castle Project - http://www.castleproject.org/
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#region 导入名称空间

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using LightWork.DynamicProxy.Generators;

#endregion

namespace LightWork.DynamicProxy.Internal
{
    public static class AttributeUtil
    {
        private static readonly IDictionary<Type, IAttributeDisassembler> Disassemblers =
            new Dictionary<Type, IAttributeDisassembler>();

        private static readonly AttributeUsageAttribute
            DefaultAttributeUsage = new AttributeUsageAttribute(AttributeTargets.All);

        public static IAttributeDisassembler FallbackDisassembler { get; set; } = new AttributeDisassembler();

        /// <summary>
        /// Registers custom disassembler to handle disassembly of specified type of attributes.
        /// </summary>
        /// <typeparam name="TAttribute">Type of attributes to handle</typeparam>
        /// <param name="disassembler">Disassembler converting existing instances of Attributes to CustomAttributeBuilders</param>
        /// <remarks>
        /// When disassembling an attribute Dynamic Proxy will first check if an custom disassembler has been registered to
        /// handle attributes of that type,
        /// and if none is found, it'll use the <see cref="FallbackDisassembler" />.
        /// </remarks>
        public static void AddDisassembler<TAttribute>(IAttributeDisassembler disassembler) where TAttribute : Attribute
        {
            if (disassembler == null)
                throw new ArgumentNullException("disassembler");

            Disassemblers[typeof(TAttribute)] = disassembler;
        }

        public static CustomAttributeBuilder CreateBuilder(CustomAttributeData attribute)
        {
            Debug.Assert(attribute != null, "attribute != null");

            PropertyInfo[] properties;
            object[] propertyValues;
            FieldInfo[] fields;
            object[] fieldValues;
            GetSettersAndFields(attribute.NamedArguments, out properties, out propertyValues, out fields,
                out fieldValues);
            var constructorArgs = GetArguments(attribute.ConstructorArguments);
            return new CustomAttributeBuilder(attribute.Constructor,
                constructorArgs,
                properties,
                propertyValues,
                fields,
                fieldValues);
        }

        private static object[] GetArguments(IList<CustomAttributeTypedArgument> constructorArguments)
        {
            var arguments = new object[constructorArguments.Count];
            for (var i = 0; i < constructorArguments.Count; i++)
                arguments[i] = ReadAttributeValue(constructorArguments[i]);

            return arguments;
        }

        private static object ReadAttributeValue(CustomAttributeTypedArgument argument)
        {
            var value = argument.Value;
            if (argument.ArgumentType.IsArray == false)
                return value;
            //special case for handling arrays in attributes
            var arguments = GetArguments((IList<CustomAttributeTypedArgument>) value);
            var array = Array.CreateInstance(argument.ArgumentType.GetElementType() ?? typeof(object),
                arguments.Length);
            arguments.CopyTo(array, 0);
            return array;
        }

        private static void GetSettersAndFields(IEnumerable<CustomAttributeNamedArgument> namedArguments,
            out PropertyInfo[] properties, out object[] propertyValues,
            out FieldInfo[] fields, out object[] fieldValues)
        {
            var propertyList = new List<PropertyInfo>();
            var propertyValuesList = new List<object>();
            var fieldList = new List<FieldInfo>();
            var fieldValuesList = new List<object>();
            foreach (var argument in namedArguments)
                switch (argument.MemberInfo.MemberType)
                {
                    case MemberTypes.Property:
                        propertyList.Add(argument.MemberInfo as PropertyInfo);
                        propertyValuesList.Add(ReadAttributeValue(argument.TypedValue));
                        break;
                    case MemberTypes.Field:
                        fieldList.Add(argument.MemberInfo as FieldInfo);
                        fieldValuesList.Add(ReadAttributeValue(argument.TypedValue));
                        break;
                    default:
                        // NOTE: can this ever happen?
                        throw new ArgumentException(string.Format("Unexpected member type {0} in custom attribute.",
                            argument.MemberInfo.MemberType));
                }

            properties = propertyList.ToArray();
            propertyValues = propertyValuesList.ToArray();
            fields = fieldList.ToArray();
            fieldValues = fieldValuesList.ToArray();
        }

        public static IEnumerable<CustomAttributeBuilder> GetNonInheritableAttributes(this MemberInfo member)
        {
            Debug.Assert(member != null, "member != null");
            var attributes =
                CustomAttributeData.GetCustomAttributes(member);

            foreach (var attribute in attributes)
            {
                var attributeType = attribute.Constructor.DeclaringType;

                if (ShouldSkipAttributeReplication(attributeType))
                    continue;

                CustomAttributeBuilder builder;
                try
                {
                    builder = CreateBuilder(attribute);
                }
                catch (ArgumentException e)
                {
                    var message =
                        string.Format(
                            "Due to limitations in CLR, DynamicProxy was unable to successfully replicate non-inheritable attribute {0} on {1}{2}. To avoid this error you can chose not to replicate this attribute type by calling '{3}.Add(typeof({0}))'.",
                            attributeType.FullName, member.ReflectedType == null ? "" : member.ReflectedType.FullName,
                            member is Type ? "" : "." + member.Name, typeof(AttributesToAvoidReplicating).FullName);
                    throw new ProxyGenerationException(message, e);
                }
                if (builder != null)
                    yield return builder;
            }
        }

        public static IEnumerable<CustomAttributeBuilder> GetNonInheritableAttributes(this ParameterInfo parameter)
        {
            Debug.Assert(parameter != null, "parameter != null");
            var attributes = CustomAttributeData.GetCustomAttributes(parameter);

            foreach (var attribute in attributes)
            {
                var attributeType = attribute.Constructor.DeclaringType;

                if (ShouldSkipAttributeReplication(attributeType))
                    continue;

                var builder = CreateBuilder(attribute);
                if (builder != null)
                    yield return builder;
            }
        }

        /// <summary>
        /// Attributes should be replicated if they are non-inheritable,
        /// but there are some special cases where the attributes means
        /// something to the CLR, where they should be skipped.
        /// </summary>
        private static bool ShouldSkipAttributeReplication(Type attribute)
        {
            if (attribute.IsPublic == false)
                return true;

            if (SpecialCaseAttributThatShouldNotBeReplicated(attribute))
                return true;

            var attrs = attribute.GetCustomAttributes(typeof(AttributeUsageAttribute), true);

            if (attrs.Length != 0)
            {
                var usage = (AttributeUsageAttribute) attrs[0];

                return usage.Inherited;
            }

            return true;
        }

        private static bool SpecialCaseAttributThatShouldNotBeReplicated(Type attribute)
        {
            return AttributesToAvoidReplicating.Contains(attribute);
        }

        public static CustomAttributeBuilder CreateBuilder<TAttribute>() where TAttribute : Attribute, new()
        {
            var constructor = typeof(TAttribute).GetConstructor(Type.EmptyTypes);
            Debug.Assert(constructor != null, "constructor != null");

            return new CustomAttributeBuilder(constructor, new object[0]);
        }

        public static CustomAttributeBuilder CreateBuilder(Type attribute, object[] constructorArguments)
        {
            Debug.Assert(attribute != null, "attribute != null");
            Debug.Assert(typeof(Attribute).IsAssignableFrom(attribute),
                "typeof(Attribute).IsAssignableFrom(attribute)");
            Debug.Assert(constructorArguments != null, "constructorArguments != null");

            var constructor = attribute.GetConstructor(GetTypes(constructorArguments));
            Debug.Assert(constructor != null, "constructor != null");

            return new CustomAttributeBuilder(constructor, constructorArguments);
        }

        // NOTE: Use other overloads if possible. This method is here to support Silverlight and legacy scenarios.
        internal static CustomAttributeBuilder CreateBuilder(Attribute attribute)
        {
            var type = attribute.GetType();

            IAttributeDisassembler disassembler;
            if (Disassemblers.TryGetValue(type, out disassembler))
                return disassembler.Disassemble(attribute);
            return FallbackDisassembler.Disassemble(attribute);
        }

        private static Type[] GetTypes(object[] objects)
        {
            var types = new Type[objects.Length];
            for (var i = 0; i < types.Length; i++)
                types[i] = objects[i].GetType();
            return types;
        }

        /// <summary>
        /// Gets the attribute.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The member attribute.</returns>
        public static T GetAttribute<T>(this ICustomAttributeProvider member) where T : class
        {
            return GetAttributes<T>(member).FirstOrDefault();
        }

        /// <summary>
        /// Gets the attributes. Does not consider inherited attributes!
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The member attributes.</returns>
        public static T[] GetAttributes<T>(this ICustomAttributeProvider member) where T : class
        {
            if (typeof(T) != typeof(object))
                return (T[]) member.GetCustomAttributes(typeof(T), false);
            return (T[]) member.GetCustomAttributes(false);
        }

        /// <summary>
        /// Gets the type attribute.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The type attribute.</returns>
        public static T GetTypeAttribute<T>(this Type type) where T : class
        {
            var attribute = GetAttribute<T>(type);

            if (attribute == null)
                foreach (var baseInterface in type.GetInterfaces())
                {
                    attribute = GetTypeAttribute<T>(baseInterface);
                    if (attribute != null)
                        break;
                }

            return attribute;
        }

        /// <summary>
        /// Gets the type attributes.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The type attributes.</returns>
        public static T[] GetTypeAttributes<T>(Type type) where T : class
        {
            var attributes = GetAttributes<T>(type);

            if (attributes.Length == 0)
                foreach (var baseInterface in type.GetInterfaces())
                {
                    attributes = GetTypeAttributes<T>(baseInterface);
                    if (attributes.Length > 0)
                        break;
                }

            return attributes;
        }

        public static object[] GetInterfaceAttributes(Type type)
        {
            return InterfaceAttributeUtil.GetAttributes(type, true);
        }

        public static AttributeUsageAttribute GetAttributeUsage(this Type attributeType)
        {
            var attributes = attributeType.GetCustomAttributes(typeof(AttributeUsageAttribute), true);
            return attributes.Length != 0
                ? (AttributeUsageAttribute) attributes[0]
                : DefaultAttributeUsage;
        }

        /// <summary>
        /// Gets the type converter.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns></returns>
        public static Type GetTypeConverter(MemberInfo member)
        {
            var attrib = GetAttribute<TypeConverterAttribute>(member);

            if (attrib != null)
                return Type.GetType(attrib.ConverterTypeName);

            return null;
        }

        /// <summary>
        /// Gets the attribute.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The member attribute.</returns>
        public static bool HasAttribute<T>(this ICustomAttributeProvider member) where T : class
        {
            return GetAttributes<T>(member).FirstOrDefault() != null;
        }
    }
}